package com.xuzhiguang.baidu.pan.sdk.operations;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xuzhiguang.baidu.pan.sdk.common.http.*;
import com.xuzhiguang.baidu.pan.sdk.configuration.BaiduPanConfiguration;
import com.xuzhiguang.baidu.pan.sdk.exception.BaiduPanSdkException;
import com.xuzhiguang.baidu.pan.sdk.model.OAuthAccessTokenResult;
import com.xuzhiguang.baidu.pan.sdk.model.OAuthErrorResult;
import com.xuzhiguang.baidu.pan.sdk.token.AccessTokenUtil;
import com.xuzhiguang.baidu.pan.sdk.token.ITokenStorage;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author xuzhiguang
 * @date 2020/6/26 1:43
 */
public class BaiduPanOAuthOperations {

    private final String urlPrefix = "https://openapi.baidu.com/oauth/2.0";

    private final ObjectMapper objectMapper = new ObjectMapper();

    private final HttpClient httpClient;

    private final ITokenStorage tokenStorage;

    private final BaiduPanConfiguration baiduPanConfiguration;

    public BaiduPanOAuthOperations(HttpClient httpClient, ITokenStorage tokenStorage, BaiduPanConfiguration baiduPanConfiguration) {
        this.httpClient = httpClient;
        this.tokenStorage = tokenStorage;
        this.baiduPanConfiguration = baiduPanConfiguration;
    }

    /**
     * 获取授权Uri
     * @param redirectUri
     * @return
     */
    public String getAuthorizeUri(String redirectUri) {
        return getAuthorizeUri(redirectUri, null, null, null, null, null, null, null, null, null, null);
    }

    /**
     * 获取授权Uri
     * @param redirectUri 授权后要回调的URI，即接收Authorization Code的URI。如果用户在授权过程中取消授权，会回调该URI，并在URI末尾附上error=access_denied参数。对于无Web Server的应用，其值可以是“oob”，此时用户同意授权后，授权服务会将Authorization Code直接显示在响应页面的页面中及页面title中。非“oob”值的redirect_uri按照如下规则进行匹配：（1）如果开发者在“授权安全设置”中配置了“授权回调地址”，则redirect_uri必须与“授权回调地址”中的某一个相匹配； （2）如果未配置“授权回调地址”，redirect_uri所在域名必须与开发者注册应用时所提供的网站根域名列表或应用的站点地址（如果根域名列表没填写）的域名相匹配。授权回调地址配置请参考附录1。
     * @param scope 以空格分隔的权限列表，若不传递此参数，代表请求用户的默认权限。可填basic或mobile。
     * @param display 登录和授权页面的展现样式，默认为“page”，具体参数定义请参考附录2。
     * @param state 重定向后会带上state参数。建议开发者利用state参数来防止CSRF攻击。
     * @param forceLogin 如传递“force_login=1”，则加载登录页时强制用户输入用户名和口令，不会从cookie中读取百度用户的登陆状态。
     * @param confirmLogin 如传递“confirm_login=1”且百度用户已处于登陆状态，会提示是否使用已当前登陆用户对应用授权。
     * @param loginType 如传递“login_type=sms”，授权页面会默认使用短信动态密码注册登陆方式。
     * @param qrextAppid 网盘扫码透传字段
     * @param bgurl 二维码登录方式的背景图片url链接，需要encode
     * @param qrcodeW 自定义二维码图片的宽度
     * @param qrcodeH 自定义二维码图片的高度
     * @return
     */
    public String getAuthorizeUri(String redirectUri, String scope, String display, String state, Integer forceLogin,
                                  Integer confirmLogin, String loginType, String qrextAppid, String bgurl, Integer qrcodeW,
                                  Integer qrcodeH) {

        StringBuilder uri = new StringBuilder(urlPrefix + "/authorize?");

        Map<String, String> parameters = new LinkedHashMap<>();

        parameters.put("response_type", "code");
        parameters.put("client_id", this.baiduPanConfiguration.getApiKey());

        if (redirectUri == null || redirectUri.length() <= 0) {
            throw new IllegalArgumentException("redirectUri 不能为空");
        }

        parameters.put("redirect_uri", redirectUri);

        if (scope != null && scope.length() <= 0) {
            parameters.put("scope", scope);
        }

        if (display != null && display.length() <= 0) {
            parameters.put("display", display);
        }

        if (state != null && state.length() <= 0) {
            parameters.put("state", state);
        }

        if (forceLogin != null) {
            parameters.put("force_login", forceLogin + "");
        }

        if (confirmLogin != null) {
            parameters.put("confirm_login", confirmLogin + "");
        }

        if (loginType != null && loginType.length() <= 0) {
            parameters.put("login_type", loginType);
        }

        if (qrextAppid != null && qrextAppid.length() <= 0) {
            parameters.put("qrext_appid", qrextAppid);
        }

        if (bgurl != null && bgurl.length() <= 0) {
            parameters.put("bgurl", bgurl);
        }

        if (qrcodeW != null) {
            parameters.put("qrcodeW", qrcodeW + "");
        }

        if (qrcodeH != null) {
            parameters.put("qrcodeW", qrcodeH + "");
        }

        uri.append(HttpUtil.parametersToString(parameters));

        return uri.toString();
    }

    /**
     * 按需刷新access_token
     * @param refreshToken
     * @return
     */
    public OAuthAccessTokenResult refreshToken(String refreshToken) throws BaiduPanSdkException {
        Map<String, String> parameters = new LinkedHashMap<>();
        parameters.put("grant_type", "refresh_token");
        parameters.put("refresh_token", refreshToken);

        return this.auth(parameters);
    }

    /**
     * 获取网页授权access_token
     * @param code
     * @param redirectUri
     * @return
     */
    public OAuthAccessTokenResult authorizationCode(String code, String redirectUri) throws BaiduPanSdkException {
        Map<String, String> parameters = new LinkedHashMap<>();
        parameters.put("grant_type", "authorization_code");
        parameters.put("code", code);
        parameters.put("redirect_uri", redirectUri);

        return this.auth(parameters);
    }

    public OAuthAccessTokenResult auth(Map<String, String> parameters) throws BaiduPanSdkException {

        String url = urlPrefix + "/token";

        parameters.put("client_id", this.baiduPanConfiguration.getApiKey());
        parameters.put("client_secret", this.baiduPanConfiguration.getApiSecret());

        JavaType javaType = this.objectMapper.constructType(OAuthAccessTokenResult.class);

        OAuthAccessTokenResult result = this.get(url, parameters, javaType);

        // 更新token
        tokenStorage.save(this.baiduPanConfiguration.getApiKey(), AccessTokenUtil.convertAccessToken(result));

        return result;
    }

    public <T extends OAuthErrorResult> T get(String url, Map<String, String> parameters, JavaType javaType) throws BaiduPanSdkException {


        Request request = new Request();
        request.setHttpMethod(HttpMethod.GET);
        request.setUrl(url);
        request.setParameters(parameters);
        Response response = httpClient.execute(request);
        T t;
        try {
            t = objectMapper.readValue(response.getContent(), javaType);
        } catch (IOException e) {
            throw new BaiduPanSdkException("反序列化失败", e);
        }
        return t;
    }


}
